package net.mhelvens.deltaprofiles;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.util.LongSparseArray;
import android.util.Log;
import android.util.SparseArray;



abstract public class Condition {
    private static final String TAG = Condition.class.getSimpleName();
	
	////////// Public Methods //////////////////////////////////////////////////
	
    ///// URIs /////
    
	public Uri uri() {
		assert isInitialized();
		
		return uri_base().buildUpon().appendPath("/"+mID) .build();
	}
	
	///// Mutators /////
	
	final public void delete() {
		assert isInitialized();
		assert !isDeleted();
		
		onDelete();
		
		DeltaProfilesDB.get().db()
			.delete(TABLE_main, KEY_id + " = " + mID, null);
		
		mDeleted = true;
		mCache.delete(mID);
		
		broadcast(ACTION_BROADCAST_DELETE, uri());
	}
	
	///// Accessors /////
	
	public boolean isDeleted() {
		assert isInitialized();
		
		return mDeleted;
	}
	
	public long id() {
		assert isInitialized();
		assert !isDeleted();
		
		return mID;
	}
	
	public String source() {
		assert isInitialized();
		assert !isDeleted();
		
		return mSource;
	}
	
	////////// Protected  Methods //////////////////////////////////////////////

	///// Constructors /////
	
	protected Condition() {
	} // At this point: still uninitilized (intentionally empty)
	
	///// To Be Overwritten /////
	
	abstract protected String initialData();
	protected void onNew()        {} // intentionally empty
	protected void onInitialize() {} // intentionally empty
	protected void onRefresh()    {} // intentionally empty
	protected void onDelete()     {} // intentionally empty
	
	protected void broadcastModify() {
		assert isInitialized();
		
		// Without the subclass, we don't know enough about
		// the data to send a more specific broadcast.
		broadcast(ACTION_BROADCAST_MODIFY, uri());
	}
	
	///// Mutators /////
	
	protected Condition setData(String data) {
		assert isInitialized();
		assert !isDeleted();
		
		mData = data;

    	ContentValues values = new ContentValues();
    	values.put(KEY_data, data);
		DeltaProfilesDB.get().db()
			.update(TABLE_main, values, KEY_id+" = "+mID, null);
		
		broadcastModify();
		
		return this;
	}
	
	///// Accessors /////
	
	protected boolean isInitialized() {
		return mInitialized;
	};
	
	protected String data() {
		assert isInitialized();
		assert !isDeleted();
		
		refresh();
		return mData;
	}
	
	////////// Package Private Methods /////////////////////////////////////////
	
	///// Mutators /////
	
	final Condition addSubcondition(int index, Condition condition) {
		assert isInitialized();
		assert !isDeleted();
		
		mSubconditions.put(index, condition);
		
    	ContentValues values = new ContentValues();
    	values.put(KEY_parent, mID           );
    	values.put(KEY_index , index         );
    	values.put(KEY_child , condition.id());
		DeltaProfilesDB.get().db().insertOrThrow(TABLE_tree, null, values);
		
		return this;
	}
	
	final Condition removeSubcondition(int index) {
		assert isInitialized();
		assert !isDeleted();
		
		mSubconditions.delete(index);
		
    	DeltaProfilesDB.get().db().delete(TABLE_tree, KEY_parent + " = " + mID
    			             + " and " + KEY_index  + " = " + index, null);
    	
		return this;
	}

	///// Accessors /////
	
	public SparseArray<Condition> allSubconditions() {
		assert isInitialized();
		assert !isDeleted();
		
		return mSubconditions;
	}

	////////// Private Fields //////////////////////////////////////////////////

	///// Bookkeeping /////

	private boolean mInitialized = false;
	private boolean mValid       = false;
	private boolean mDeleted     = false;
	
	///// Unique Database ID /////
	
	private long mID;

	///// Data-items and Defaults /////
	
	private String mSource                        = null;
	private String mData                          = null;
	private SparseArray<Condition> mSubconditions = new SparseArray<Condition>();
	
	////////// Private Methods /////////////////////////////////////////////////
	
	///// Mutators /////
	
	private Condition initializeAsNew() {
		assert !isInitialized();
		
		mID = DeltaProfilesDB.get().db()
    		.insertOrThrow(TABLE_main, null, valuesForDB(initialData()));
    	
    	setInitialized();
    	
		setValid();
    	
		onNew();
		
		onInitialize();
		
		broadcast(ACTION_BROADCAST_ADD, uri());
		
		return this;
	}
	
	private Condition initializeAsExisting(long id) {
		assert !isInitialized();
		
		mID = id;
		
    	setInitialized();
		
		onInitialize();
		
		return this;
	}
	
	///// Utility Methods /////
	
	private ContentValues valuesForDB() { return valuesForDB(mData); }
	private ContentValues valuesForDB(String data) {
		ContentValues result = new ContentValues();
		result.put(KEY_source, mSource );
		result.put(KEY_data  , data    );
		return result;
	}
	
	private void fetchInitialSubconditionsFromDB() {
		assert mSubconditions.size() == 0;
		
		Cursor c = DeltaProfilesDB.get().db().query(
			VIEW_directsubconditions, null, KEY_parent+" = "+mID,
			null, null, null, null
		);
		
		for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
			mSubconditions.put(
				c.getInt(c.getColumnIndex(KEY_index)),
				get(c.getLong(c.getColumnIndex(KEY_child)))
			);
		}
	}
	
	private void setValuesFromDB(Cursor c) {
		assert !c.isBeforeFirst() && !c.isAfterLast();
		
		mSource = c.getString(c.getColumnIndex(KEY_source));
		mData   = c.getString(c.getColumnIndex(KEY_data  ));
	}

	private void broadcast(String action, Uri uri) { broadcast(action, uri, null); }
	private void broadcast(String action, Uri uri, Bundle extras) {
		LocalBroadcastManager.getInstance(mContext).sendBroadcast(
			new Intent(action, uri())
				.putExtra("id", mID)
		);
	}
	
	private void setInitialized() { mInitialized = true; }
	
	private void setInvalid() { mValid = false; }
	private void setValid()   { mValid = true ; }
	
	private void refresh() { refresh(null); }
	private void refresh(Cursor c) {
		assert !isDeleted();
		if (!mValid) {
			// is a positioned cursor supplied already? otherwise, perform query
			if (c == null) {
				c = DeltaProfilesDB.get().db().query(TABLE_main, null, KEY_id+" = "+mID, null, null, null, null);
				c.moveToFirst();
			}
			
			setValuesFromDB(c);
			
			// we do not 'refresh' subconditions, just initialize them;
			// we then maintain the list at every change
			if (mSubconditions == null) fetchInitialSubconditionsFromDB();
			
			setValid();
		}
	}
	
	////////// Static //////////////////////////////////////////////////////////
	
	///// Public Methods /////
	
	static public Uri uri_base() { return new Uri.Builder().path("/conditions").build(); }

	static public void setContext(Context context) {
		mContext = context;
	}
	
	static public Long idOf(Condition condition) {
		if (condition == null) return null;
		return condition.id();
	}
	
    static public Condition getNew(String source) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
    	Condition result = getInstance(source);
    	result.initializeAsNew();
    	return result;
    }
    
    static public Condition get(long id) { return get(id, null); }
    
	///// Broadcast Actions /////
	
	static public final String ACTION_BROADCAST_ADD    = "net.mhelvens.deltaprofiles.Condition.ACTION_BROADCAST_ADD";
	static public final String ACTION_BROADCAST_MODIFY = "net.mhelvens.deltaprofiles.Condition.ACTION_BROADCAST_MODIFY";
	static public final String ACTION_BROADCAST_DELETE = "net.mhelvens.deltaprofiles.Condition.ACTION_BROADCAST_DELETE";
	
	///// Database Constants /////
	
    //================================================================
    static private final String TABLE_main = "conditions";
    //----------------------------------------------------------------
	static private final String KEY_id     = BaseColumns._ID;
    static private final String KEY_source = "source";
    static private final String KEY_data   = "data";
    //================================================================
    static private final String TABLE_tree = "condition_tree";
    //----------------------------------------------------------------
    static private final String KEY_parent = "parent";
    static private final String KEY_index  = "child_index";
    static private final String KEY_child  = "child";
    //=====================================================================
    static private final String VIEW_directsubconditions = "subconditions";
    //=====================================================================

	///// Private Utility Methods /////
	
    static private boolean isCached(long id) {
    	return mCache.get(id, null) != null;
    }
	
    static private Condition getInstance(String source) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    	try {
    		Condition result = Class.forName(source).asSubclass(Condition.class).newInstance();
    		mCache.put(result.id(), result);
			return result;
		} catch (ClassNotFoundException e) {
			Log.e(TAG,
				"I tried to find Condition subclass '"
				+ source + "', but I couldn't. "
				+ "This is probably a bug in a plugin.", e);
			throw e;
		} catch (InstantiationException e) {
			Log.e(TAG,
				"I tried to instantiate Condition subclass '"
				+ source + "', but I couldn't. "
				+ "This is probably a bug in a plugin.", e);
			throw e;
		} catch (IllegalAccessException e) {
			Log.e(TAG,
					"I tried to access the default constructor of Condition subclass '"
					+ source + "', but I couldn't. "
					+ "This is probably a bug in a plugin.", e);
			throw e;
		}
    	// TODO: Properly handle these errors in the app,
    	//       showing an error message to the user
    }
    
    static private Condition get(Long id, Cursor c) {
    	if (id == null || id <= 0)
    		return null;
    	
    	if (c == null) {
    		c = DeltaProfilesDB.get().db().query(
    			TABLE_main, null, KEY_id+" = "+id,
    			null, null, null, null
    		);
    	}
    	
    	if (!isCached(id)) {
    		final String errorMessage =
    			"That should not have happened for " +
    			"a condition already in the database!";
			try {
				mCache.put(id, getInstance(c.getString(c.getColumnIndex(KEY_source))));
			} catch (ClassNotFoundException e) {
				Log.e(TAG, errorMessage);
			} catch (InstantiationException e) {
				Log.e(TAG, errorMessage);
			} catch (IllegalAccessException e) {
				Log.e(TAG, errorMessage);
			}
    	}
    	
    	mCache.get(id).refresh(c);
    	
    	return mCache.get(id);
    }
    
	///// Private Fields /////

	static private Context                    mContext;
	static private LongSparseArray<Condition> mCache = new LongSparseArray<Condition>();
}
